import copy
from functools import lru_cache
from typing import Any, List

from .field_base import Field, Validate, VerifyResult


class ValidateErr(Exception):
    '''校验失败'''

    def __init__(self, msg: str = ''):
        self.msg = msg

class ConvertErr(ValidateErr):
    def __init__(self, msg=''):
        '''类型转换失败'''
        self.msg = msg


class RequiredMissingErr(ValidateErr):
    def __init__(self, msg=''):
        '''必要属性缺失'''
        self.msg = msg


class SchemaMetaClass(type):

    def __new__(cls, name, bases, attrs: dict):
        __fields__ = dict()
        __protected_attrs__ = set()
        __required_attrs__ = set()
        __default_data__ = dict()
        for base in bases:
            if hasattr(base, '__fields__'):
                __fields__ .update(base.__fields__)
            if hasattr(base, '__default_data__'):
                __default_data__.update(base.__default_data__)
        for k, v in attrs.items():
            if isinstance(v, Field):
                __fields__[k] = v
        for k, v in __fields__.items():
                if v.protected:
                    __protected_attrs__.add(k)
                if v.required:
                    __required_attrs__.add(k)
                if v.default is not None:
                    __default_data__[k] = v.default

        attrs['__fields__'] = __fields__
        attrs['__protected_attrs__'] = __protected_attrs__
        attrs['__required_attrs__'] = __required_attrs__
        attrs['__default_data__'] = __default_data__
        return super().__new__(cls, name, bases, attrs)


class Schema(metaclass=SchemaMetaClass):

    def __init__(self, **data):
        self.__version__ = 0
        self.__read_version__ = 0
        for k, v in data.items():
            if k in self.__fields__:
                self.__setattr__(k, v)
            else:
                setattr(self, k, v)

    def validate(self):
        message = []
        for name, _ in self.__fields__.items():
            field: Field = object.__getattribute__(self, name)
            if isinstance(field, NestedField):
                res = field.value.validate()
            else:
                res = field.validate()
                if not res.ok:
                    message.append(f'{name}{res.msg_suffix}')
        if message:
            raise ValidateErr(','.join(message))

    @lru_cache(maxsize=1)
    def __to_dict(self, _, to_json=True) -> dict:
        self.validate()
        d = {}

        for k, _ in self.__fields__.items():
            field: Field = object.__getattribute__(self, k)
            if isinstance(field, NestedField):
                v = field.output()
                if to_json:
                    d[k] = v.to_dict()
                else:
                    d[k] = v
            else:
                if field.json_key:
                    k = field.json_key
                d[k] = field.output()
        object.__setattr__(self, '__read_version__', object.__getattribute__(self, '__read_version__') + 1)
        return d

    @classmethod
    def field_keys(cls) -> List[str]:
        '''字段名称列表'''
        return list(cls.__fields__.keys())

    def to_dict(self):
        return self.__to_dict(self.__version__)

    def __setattr__(self, name: str, value: Any) -> None:
        fields = object.__getattribute__(self, '__fields__')
        if name in fields:
            object.__setattr__(self, '__version__', object.__getattribute__(self, '__version__') + 1)
            field: Field = copy.copy(fields[name])
            if isinstance(field, NestedField) and isinstance(value, dict):
                field.value = field.model(**value)
            else:
                field.value = value
            object.__setattr__(self, name, field)
        else:
            super(Schema, self).__setattr__(name, value)

    def __getattribute__(self, name: str) -> Any:
        fields = object.__getattribute__(self, '__fields__')
        if name in fields:
            val: Field = object.__getattribute__(self, name)
            return val.output()
        return object.__getattribute__(self, name)

    def __repr__(self):
        return (f'<<{self.__class__.__name__}>> {self.__to_dict(self.__read_version__, to_json=False)}')

    def __str__(self):
        return self.__repr__()


class NestedField(Field):

    def __init__(self,
                 model: Schema,
                 required: bool = False,
                 json_key: str = '',
                 default: Any = None,
                 protected: bool = False,
                 validators: List[Validate] = None,
                 desc: str = '') -> None:
        self.model = model
        super().__init__(required, json_key, default, protected, validators, desc)

    def output(self) -> Schema:
        return self.value

    def validate(self) -> None:
        self.model.validate()